package similarity.simiMatrix;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import utils.Configuration;

/**
 * calculate the similarity matrix by only consider the diagonal items in
 * similarity matrix, and set non-diagonal items value to 0
 * 
 * @author eDisOn
 * @version 0.1
 * 
 */

@SuppressWarnings("unused")
public class CalcSimMatrixByDiagonal implements SimilarityMatrix
{
    /* Project root path */
    private final static String ROOT                  = utils.Configuration
                                                              .RootPath;

    /* Similarity matrix file path */
    private final static String SIMI_MATRIX_FILE_PATH = ROOT;

    /* DTD file */
    private Document            DTD                   = null;

    /* The total number of elements in this DTD file */
    private int                 totalElemNum          = 0;

    /*
     * DFSLableIndex is used to assist DFS process to set label to each element
     * in DFS traversal
     */
    private int                 DFSLableIndex         = 0;

    /*
     * matrixLabel record the elements name in it according to element's DFS
     * order
     */
    private String[]            matrixLabel           = null;

    /* depthLabel is used to record the depth information for all elements */
    private int[]               depthLabel            = null;

    /* simiMatrix record the similarity between each pair of elements */
    double[][]                  simiMatrix            = null;

    /**
     * Default construction used when similarity matrix has been calculated and
     * saved into local disk, and user just want to load it into memory.
     */
    public CalcSimMatrixByDiagonal()
    {

    }

    /**
     * Construction with DTD parameter
     */
    public CalcSimMatrixByDiagonal(Document DTD)
    {
        /* get the total element number info */
        this.DTD = DTD;
        this.totalElemNum = 1 + this.calcElemNum(this.DTD.getRootElement());

        /* first create a matrix table */
        this.simiMatrix = new double[this.totalElemNum][this.totalElemNum + 1];

        /*
         * matrixLabel is used to record the labels name which consisted with
         * the elements' name and depth information(For example: if element A is
         * in the root element, then its name in matrixLabel is A_1). It's index
         * is its appearance order in DFS traversal order
         */
        this.matrixLabel = new String[this.totalElemNum];

        /* depthLabel is used to record depth information for each element */
        this.depthLabel = new int[this.totalElemNum];
    }

    /**
     * calculate the similarity matrix by only consider the diagonal items in
     * similarity matrix, and set non-diagonal items value to 0
     * 
     * @param trainDocList
     *            training xml document set
     * @param DTD
     *            DTD file
     * 
     * @return true if calculate succeed, false otherwise
     */
    public boolean calcSimMatrix(ArrayList<Document> trainDocList, Document DTD)
    {
        /*
         * DFS - explore all element in DTD tree and set their info into
         * matrixLabel according to them order number in DFS traversal
         */
        Element root = DTD.getRootElement();
        this.DFSLableIndex = -1;
        this.DFSExplore(root);

        /*
         * calculate the similarity matrix by only consider the diagonal items
         * in similarity matrix, and set non-diagonal items value to 0
         */
        this.calcSimilarity();

        return false;
    }

    /**
     * DFS - explore all element in DTD tree and set their info into matrixLabel
     * according to them order number in DFS traversal
     * 
     * @param elem
     *            current analysis element
     * @return true if explore succeed, false otherwise
     */
    private boolean DFSExplore(Element elem)
    {
        try
        {
            this.DFSLableIndex++;
            this.matrixLabel[this.DFSLableIndex] = elem.getName();

            int childIndex = 1;
            for (Iterator it = elem.elementIterator(); it.hasNext(); childIndex++)
            {
                Element child = (Element) it.next();
                this.DFSExplore(child);
            }

            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    /**
     * Calculate the similarity value in the diagonal item in similarity matrix
     * 
     * @return true if calculate succeed, false otherwise
     */
    private boolean calcSimilarity()
    {
        try
        {
            for (int index = 0; index < this.totalElemNum; index++)
            {
                this.simiMatrix[index][index] = 1;
            }
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    /**
     * Calculate all descendant element number in this element object
     * 
     * @param elem
     *            Element object
     * @return the all descendant element number in this element object
     */
    private int calcElemNum(Element elem)
    {
        int childrenNum = 0;
        for (Iterator it = elem.elementIterator(); it.hasNext();)
        {
            this.calcElemNum((Element) it.next());
            childrenNum++;
        }

        this.totalElemNum += childrenNum;
        return this.totalElemNum;
    }

    /**
     * Get similarity matrix
     * 
     * @return similarity matrix
     */
    public double[][] getSimilarityMatrix()
    {
        return this.simiMatrix;
    }

    /**
     * Display the similarity matrix
     */
    public void displaySimiMatrix()
    {
        for (int i = 0; i < this.totalElemNum; i++)
        {
            for (int j = 0; j < this.totalElemNum; j++)
            {
                System.out.print(this.simiMatrix[i][j] + "\t");
            }
            System.out.println();
        }
    }

    /**
     * Save the similarity matrix content to local disk
     * 
     * @param simiMatrixPath
     *            the similarity matrix file path in local disk
     * @return true if save succeed, false otherwise
     */
    public boolean saveSimilarityMatrix()
    {
        try
        {
            FileWriter fw = new FileWriter(this.SIMI_MATRIX_FILE_PATH);
            PrintWriter fpw = new PrintWriter(fw);

            /* Write the matrix label info into first line */
            for (int i = 0; i < this.totalElemNum; i++)
            {
                fpw.print(this.matrixLabel[i] + "\t");
            }
            fpw.println();

            /* Write the depth info into second line */
            for (int i = 0; i < this.totalElemNum; i++)
            {
                fpw.print(this.depthLabel[i] + "\t");
            }
            fpw.println();

            /* Write the similarity value into file */
            for (int i = 0; i < this.totalElemNum; i++)
            {
                for (int j = 0; j < this.totalElemNum; j++)
                {
                    fpw.print(this.simiMatrix[i][j] + "\t");
                }
                fpw.println();
            }
            fpw.close();
            fw.close();
            return true;
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
            return false;
        }
    }

    /**
     * Load the similarity matrix content from local disk
     * 
     * @param simiMatrixPath
     *            the similarity matrix file path in local disk
     * @return true if load succeed, false otherwise
     */
    public boolean loadSimilarityMatrix()
    {
        try
        {
            BufferedReader fr = new BufferedReader(new FileReader(
                    this.SIMI_MATRIX_FILE_PATH));

            /* get total element number */
            String line = fr.readLine();
            StringTokenizer stk = new StringTokenizer(line, "\t");
            this.totalElemNum = stk.countTokens();

            /* load matrix label info */
            this.matrixLabel = new String[this.totalElemNum];
            int index = 0;
            while (stk.hasMoreTokens())
            {
                this.matrixLabel[index] = stk.nextToken();
                index++;
            }

            /* load depth info */
            this.depthLabel = new int[this.totalElemNum];
            line = fr.readLine();
            stk = new StringTokenizer(line, "\t");
            index = 0;
            while (stk.hasMoreTokens())
            {
                this.depthLabel[index] = Integer.parseInt(stk.nextToken());
                index++;
            }

            /* load similarity value */
            this.simiMatrix = new double[this.totalElemNum][this.totalElemNum];
            index = 0;
            while ((line = fr.readLine()) != null)
            {
                stk = new StringTokenizer(line, "\t");

                int pos = 0;
                while (stk.hasMoreTokens())
                {
                    this.simiMatrix[index][pos] = Double.parseDouble(stk
                            .nextToken());
                    pos++;
                }
                index++;
            }

            fr.close();
            return true;
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
            return false;
        }
    }

    // TODO: Test function
    public static void main(String[] args) throws DocumentException
    {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(ROOT + "data/dtd/DTD.xml");

        CalcSimMatrixByDiagonal calcByDiag = new CalcSimMatrixByDiagonal(
                document);
        calcByDiag.calcSimMatrix(null, document);
        calcByDiag.displaySimiMatrix();
        calcByDiag.saveSimilarityMatrix();
        // CalcSimMatrixByPathDist calcByDist = new CalcSimMatrixByPathDist();
        // calcByDist.loadSimilarityMatrix();
    }
}
